// noinspection JSE[C]alidateJSDoc,JSUnusedGlobalSymbols,JSUnusedLocalSymbols

import { ArrayTool, Consumer, ObjectTool } from '@es-tool/core'
import { Query } from 'database-ql/dist/commonjs/query'
import { CountRes, GetRes, RemoveRes } from 'database-ql/src/result-types'
import { Db } from 'laf-client-sdk'
import { Sealed } from './annotation/Sealed'
import { LafWrapperConfig } from './LafWrapperConfig'
import { Logger } from './log/Logger'
import { Page } from './model/Page'
import { QueryChain } from './QueryChain'
import { CountResponse } from './type/CountResponse'
import { ListResponse } from './type/ListResponse'
import { WithArg } from './type/WithArg'

// noinspection JSUnusedGlobalSymbols,JSUnusedLocalSymbols
/**
 * QueryChainWrapper
 * @author LL
 * @date 2022-01-11 上午 10:10
 **/
@Sealed
export class QueryChainWrapper<E = Record<string, any>> extends QueryChain<E> {
    /**
     * 引用的表名
     * @type {string}
     * @private
     */
    private readonly tableName: string
    /**
     * 日志记录器
     * @type {Logger}
     * @private
     */
    private log: Logger

    /* 临时变量缓存区 ------------------------------------  */

    private withFlag: boolean = false
    private withArg: WithArg[] = []
    private withOneFlag: boolean = false
    private withOneArg: WithArg[] = []

    /* 缓存区结束 ------------------------------------ */

    /**
     * 构造
     * @param {string} tableName 表名
     * @param {Db | null} database 数据库引用, 可空; 仅仅在需要独立指定数据库引用时候使用
     */
    constructor(tableName: string, database: Db | null = null) {
        super(database as Db ?? LafWrapperConfig.database())
        this.tableName = tableName
        this.log = LafWrapperConfig.LoggerFactory.getLogger(`QueryWrapper#${ this.tableName }`)
    }

    /**
     * 原版 with,  query 参数可以由 {@link QueryChainWrapper#getWithArg} 获得
     * @param withArg {{
     *   query: any,
     *   localField: string,
     *   foreignField: string,
     *   as: string
     * }}
     */
    public with(withArg: WithArg) {
        this.withFlag = true
        this.withArg.push(withArg)
        return this
    }

    /**
     * 原版 with,  query 参数可以由 {@link QueryChainWrapper#getWithArg} 获得
     * @param withArg {{
     *   query: any,
     *   localField: string,
     *   foreignField: string,
     *   as: string
     * }}
     */
    public withOne(withArg: WithArg) {
        this.withOneFlag = true
        this.withOneArg.push(withArg)
        return this
    }

    /**
     * 获得 with 中的 参数
     * @param foreignField {string} 关联表 关联字段名 一般是对方主键或唯一列
     * @param localField {string} 当前表 外键列属性名
     * @param as {string} 关联数据别名
     * @return
     */
    public getWithArg<T>(foreignField: keyof E, localField: keyof T, as?: string): WithArg {
        let conn: Query = this.database.collection(this.tableName).where(this.getWhereArg())
        conn = this.setOrder(conn)
        conn = this.setField(conn)
        // @ts-ignore
        return { query: conn, foreignField, localField, as }
    }

    /* 最终操作: 执行查询 -------------------------------------------------------------------------------------- */
    /**
     * 计数查询
     * @return {Promise<number>}
     */
    public async count(): Promise<number> {
        const countRes: CountRes = await this.database.collection(this.tableName)
            .where(this.getWhereArg())
            .count()
        return CountResponse.getTotal(countRes)
    }

    /**
     * 列表查询
     * @param {number} size 默认100, 最大 1000
     * @return {Promise<E[]>}
     */
    public async list(size: number = 100): Promise<E[]> {
        this.log.trace('Query List Size = ', size)
        // @ts-ignore
        let connection: Query = this.database.collection(this.tableName)
            .where(this.getWhereArg())

        connection = this.setField(connection)
        connection = this.setOrder(connection)
        connection = this.setWith(connection)

        const res: GetRes<E> = await connection.limit(size).get<E>()

        let list = ListResponse.getData<E>(res)
        this.log.trace('Query List 响应: ', list)

        return list
    }

    /**
     * Get One
     * @return {Promise<E | null>}
     */
    public async one(): Promise<E | null> {
        let connection: Query = this.database.collection(this.tableName)
            .where(this.getWhereArg())

        connection = this.setField(connection)
        connection = this.setWith(connection)

        const res = await connection.get()
        const list: E[] = ListResponse.getData(res)

        if (ArrayTool.isEmpty(list)) {
            this.log.trace('Query One: 响应 NULL')
            return null
        }
        if (list.length > 1) {
            this.log.warn('query one 警告: 期望获得 1 个结果, 实际获得: ', list.length)
        }
        this.log.trace('Query One: 响应: ', list[0])
        return list[0]!
    }

    /**
     * 分页查询
     * @param {Pick<Page<E>, 'currentPage' | 'pageSize'>} page 分页参数, 最小值 1; 默认值 1 20
     * @return {Promise<Page<E>>} 查询结果
     */
    public async page(page: Pick<Page<E>, 'currentPage' | 'pageSize'>): Promise<Page<E>> {
        this.log.trace(`Query Page 当前页: ${ page?.currentPage } 页大小: ${ page?.pageSize }`)
        const count = await this.count()
        const queryPage = Page.buildQueryPage<E>(page?.currentPage ?? 1, page?.pageSize ?? 20)

        if (count <= 0) {
            this.log.trace('Query Page 结果集为空')
            return queryPage.buildResponsePage([], 0)
        }
        let connection = this.database.collection(this.tableName)
            .where(this.getWhereArg())

        connection = this.setField(connection)
        connection = this.setOrder(connection)
        connection = this.setWith(connection)

        // noinspection TypeScriptValidateJSTypes
        const res = await connection
            .limit(queryPage.pageSize)
            .skip(queryPage.skip())
            .get()
        const list = ListResponse.getData<E>(res)
        const pageRes = queryPage.buildResponsePage(list, count)
        this.log.trace('Query Page 响应: ', pageRes)

        return pageRes
    }

    /**
     * 批量删除
     * 默认值能删除多个匹配项中的一个, 多选需要传递 multi 为 true
     * @param {boolean} multi 批量删除
     * @return {Promise<boolean>} 是否成功
     */
    public async delete(multi: boolean = false): Promise<boolean> {
        const res: RemoveRes = await this.database.collection(this.tableName)
            .where(this.getWhereArg())
            .remove({ multi })
        if (res?.ok) {
            this.log.trace('删除: ', res?.deleted)
            return true
        }
        throw new Error(res?.error ?? '删除错误')
    }

    /* 最终操作结束 -------------------------------------------------------------------------------------- */

    protected override setField(connection: Query) {
        if (ObjectTool.isEmpty(this.tempField)) {
            return connection
        }
        // 设置了 show / hide
        let isShow: boolean | null = null
        for (const kv of ObjectTool.toArray(this.tempField)) {
            if (isShow === null) {
                isShow = kv.value
                continue
            }
            if (isShow === kv.value) {
                continue
            }
            this.log.warn('show 和 hide 不能同时使用!', this.tempField)
            // 这里应该抛出错误, 或者等待 lafClientSdk 报错
        }

        // 检查关联列
        const checkWithArg: Consumer<Array<WithArg>> = (withArr) => {
            if (ArrayTool.isEmpty(withArr)) {
                return
            }

            const localFieldArr = withArr.map(i => i.localField)
            for (const field of localFieldArr) {
                // 显示白名单(show), 不包含 localField
                if (isShow) {
                    if (this.tempField[field] === undefined) {
                        this.tempField[field] = true
                        this.log.warn('show 缺少 with.localField 列, 已自动添加')
                    }
                }
                // 显示黑名单(hide), 包含 localField
                else {
                    if (this.tempField[field] !== undefined) {
                        delete this.tempField[field]
                        this.log.warn('hide 不能隐藏 with.localField 列, 已自动移除')
                    }
                }
            }
        }

        checkWithArg(this.withOneArg)
        checkWithArg(this.withArg)

        // 设置列显隐
        // @ts-ignore
        connection = connection.field(this.tempField)
        return connection
    }

    private setWith(connection: Query) {
        if (this.withFlag) {
            for (let w of this.withArg) {
                connection = connection.with(w)
            }
        }
        if (this.withOneFlag) {
            for (let w of this.withOneArg) {
                connection = connection.withOne(w)
            }
        }
        return connection
    }
}
